package utils

import (
	"encoding/binary"
	"fmt"
	log "github.com/cihub/seelog"
	"sync/atomic"
)



const (
	DEVICES_STATUS_HASH = "gateway_devices_status"
	DEVICES_VALUE_STRING = "gateway_devices_value"
	DEVICE_TYPE_INFO = "device_type_info:"
	CUSTOM_ALARM_INFO = "custom_alarm_info"
	GATEWAY_SITE_XINGONG = "gateway_site_xingong"
)

// proto address limit.
const (
	AddressBroadCast = 0
	AddressMin       = 1
	addressMax       = 247
)

// AddressMax proto address max limit
// you can change with SetSpecialAddressMax,
// when your device have address upon addressMax
var AddressMax byte = addressMax

const (
	pduMinSize = 1   // funcCode(1)
	pduMaxSize = 253 // funcCode(1) + data(252)

	rtuAduMinSize = 4   // address(1) + funcCode(1) + crc(2)
	rtuAduMaxSize = 256 // address(1) + PDU(253) + crc(2)

	asciiAduMinSize       = 3
	asciiAduMaxSize       = 256
	asciiCharacterMaxSize = 513

	tcpProtocolIdentifier = 0x0000
	// Modbus Application Protocol
	tcpHeaderMbapSize = 7 // MBAP header
	tcpAduMinSize     = 8 // MBAP + funcCode
	tcpAduMaxSize     = 260
)

// proto register limit
const (
	// Bits
	ReadBitsQuantityMin  = 1    // 0x0001
	ReadBitsQuantityMax  = 2000 // 0x07d0
	WriteBitsQuantityMin = 1    // 1
	WriteBitsQuantityMax = 1968 // 0x07b0
	// 16 Bits
	ReadRegQuantityMin             = 1   // 1
	ReadRegQuantityMax             = 125 // 0x007d
	WriteRegQuantityMin            = 1   // 1
	WriteRegQuantityMax            = 123 // 0x007b
	ReadWriteOnReadRegQuantityMin  = 1   // 1
	ReadWriteOnReadRegQuantityMax  = 125 // 0x007d
	ReadWriteOnWriteRegQuantityMin = 1   // 1
	ReadWriteOnWriteRegQuantityMax = 121 // 0x0079
)

// Function Code
const (
	// Bit access
	FuncCodeReadDiscreteInputs = 2
	FuncCodeReadCoils          = 1
	FuncCodeWriteSingleCoil    = 5
	FuncCodeWriteMultipleCoils = 15

	// 16-bit access
	FuncCodeReadInputRegisters         = 4
	FuncCodeReadHoldingRegisters       = 3
	FuncCodeWriteSingleRegister        = 6
	FuncCodeWriteMultipleRegisters     = 16
	FuncCodeReadWriteMultipleRegisters = 23
	FuncCodeMaskWriteRegister          = 22
	FuncCodeReadFIFOQueue              = 24
	FuncCodeOtherReportSlaveID         = 17
	// FuncCodeDiagReadException          = 7
	// FuncCodeDiagDiagnostic             = 8
	// FuncCodeDiagGetComEventCnt         = 11
	// FuncCodeDiagGetComEventLog         = 12
)

// Exception Code
const (
	ExceptionCodeIllegalFunction                    = 1
	ExceptionCodeIllegalDataAddress                 = 2
	ExceptionCodeIllegalDataValue                   = 3
	ExceptionCodeServerDeviceFailure                = 4
	ExceptionCodeAcknowledge                        = 5
	ExceptionCodeServerDeviceBusy                   = 6
	ExceptionCodeNegativeAcknowledge                = 7
	ExceptionCodeMemoryParityError                  = 8
	ExceptionCodeGatewayPathUnavailable             = 10
	ExceptionCodeGatewayTargetDeviceFailedToRespond = 11
)


// protocolTCPHeader independent of underlying communication layers.
type protocolTCPHeader struct {
	transactionID uint16
	protocolID    uint16
	length        uint16
	slaveID       uint8 // only modbus RTU and ascii
}

// ProtocolDataUnit (PDU) is independent of underlying communication layers.
type ProtocolDataUnit struct {
	FuncCode byte
	Data     []byte
}

// protocolFrame protocol frame in pool
type protocolFrame struct {
	adu []byte
}


// uint162Bytes creates a sequence of uint16 data.
func uint162Bytes(value ...uint16) []byte {
	data := make([]byte, 2*len(value))
	for i, v := range value {
		binary.BigEndian.PutUint16(data[i*2:], v)
	}
	return data
}


// encode modbus application protocol header & pdu to TCP frame,return adu.
//  ---- MBAP header ----
//  Transaction identifier: 2 bytes
//  Protocol identifier: 2 bytes
//  Length: 2 bytes
//  Unit identifier: 1 byte
//  ---- data Unit ----
//  Function code: 1 byte
//  Data: n bytes

func  encodeTCPFrame(tid uint16, slaveID byte,
	pdu ProtocolDataUnit) (protocolTCPHeader, []byte, error) {
	length := tcpHeaderMbapSize + 1 + len(pdu.Data)
	if length > tcpAduMaxSize {
		return protocolTCPHeader{}, nil, fmt.Errorf("modbus: length of data '%v' must not be bigger than '%v'",
			length, tcpAduMaxSize)
	}

	head := protocolTCPHeader{
		tid,
		tcpProtocolIdentifier,
		uint16(2 + len(pdu.Data)), // sizeof(SlaveId) + sizeof(FuncCode) + Data
		slaveID,
	}

	// fill adu buffer
	adu := make([]byte,length)
	binary.BigEndian.PutUint16(adu, head.transactionID)  // MBAP Transaction identifier
	binary.BigEndian.PutUint16(adu[2:], head.protocolID) // MBAP Protocol identifier
	binary.BigEndian.PutUint16(adu[4:], head.length)     // MBAP Length
	adu[6] = head.slaveID                                // MBAP Unit identifier
	adu[tcpHeaderMbapSize] = pdu.FuncCode                // PDU funcCode
	copy(adu[tcpHeaderMbapSize+1:], pdu.Data)            // PDU data
	return head, adu, nil
}


// Request:
//  Slave Id              : 1 byte
//  Function code         : 1 byte (0x01)
//  Starting address      : 2 bytes
//  Quantity of coils     : 2 bytes
// Response:
//  Function code         : 1 byte (0x01)
//  Byte count            : 1 byte
//  Coil status           : N* bytes (=N or N+1)
//  return coils status
func  ReadCoils(slaveID byte, address, quantity uint16,transaction uint32) ([]byte, error) {
	if slaveID < AddressMin || slaveID > AddressMax {
		return nil, fmt.Errorf("modbus: slaveID '%v' must be between '%v' and '%v'",
			slaveID, AddressMin, AddressMax)
	}
	if quantity < ReadBitsQuantityMin || quantity > ReadBitsQuantityMax {
		return nil, fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v'",
			quantity, ReadBitsQuantityMin, ReadBitsQuantityMax)
	}

	tid := uint16(atomic.AddUint32(&transaction,1))


	head, aduRequest, err := encodeTCPFrame(tid, slaveID, ProtocolDataUnit{
		FuncCodeReadHoldingRegisters,
			uint162Bytes(address, quantity),
		})

	if err !=nil{
		log.Infof("encodeTCPFrame Fail")
		return nil, err
	}

	fmt.Println(head)

	return aduRequest, nil
}



func  ReadHoldingRegistersBytes(slaveID byte, address, quantity uint16) ([]byte, error) {
	//if slaveID < AddressMin || slaveID > AddressMax {
	//	return nil, fmt.Errorf("modbus: slaveID '%v' must be between '%v' and '%v'",
	//		slaveID, AddressMin, AddressMax)
	//}
	//if quantity < ReadRegQuantityMin || quantity > ReadRegQuantityMax {
	//	return nil, fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v'",
	//		quantity, ReadRegQuantityMin, ReadRegQuantityMax)
	//}
	//response, err := sf.Send(slaveID, ProtocolDataUnit{
	//	FuncCode: FuncCodeReadHoldingRegisters,
	//	Data:     uint162Bytes(address, quantity),
	//})
	//
	//switch {
	//case err != nil:
	//	return nil, err
	//case len(response.Data)-1 != int(response.Data[0]):
	//	return nil, fmt.Errorf("modbus: response data size '%v' does not match count '%v'",
	//		len(response.Data)-1, response.Data[0])
	//case uint16(response.Data[0]) != quantity*2:
	//	return nil, fmt.Errorf("modbus: response data size '%v' does not match quantity to bytes '%v'",
	//		response.Data[0], quantity*2)
	//}
	//return response.Data[1:], nil
	return nil,nil
}

// Request:
//  Slave Id              : 1 byte
//  Function code         : 1 byte (0x03)
//  Starting address      : 2 bytes
//  Quantity of registers : 2 bytes
// Response:
//  Function code         : 1 byte (0x03)
//  Byte count            : 1 byte
//  Register value        : N 2-bytes
func ReadHoldingRegisters(slaveID byte, address, quantity uint16,transaction uint32) ([]byte, error) {
	if slaveID < AddressMin || slaveID > AddressMax {
		return nil, fmt.Errorf("modbus: slaveID '%v' must be between '%v' and '%v'",
			slaveID, AddressMin, AddressMax)
	}
	if quantity < ReadBitsQuantityMin || quantity > ReadBitsQuantityMax {
		return nil, fmt.Errorf("modbus: quantity '%v' must be between '%v' and '%v'",
			quantity, ReadBitsQuantityMin, ReadBitsQuantityMax)
	}

	tid := uint16(atomic.AddUint32(&transaction,1))


	head, aduRequest, err := encodeTCPFrame(tid, slaveID, ProtocolDataUnit{
		FuncCodeReadHoldingRegisters,
		uint162Bytes(address, quantity),
	})

	if err !=nil{
		log.Infof("encodeTCPFrame Fail")
		return nil, err
	}

	fmt.Println(head)

	return aduRequest, nil
}
